﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Arch.Common.Mapping
{
    public class ReflectionMapper : IMapper
    {
        public ReflectionMapper(ITypeResolver typeResolver)
        {
            //MappedTypes = new MapperCollection<Type, Type>();
            TypeResolver = typeResolver;
        }

        public ITypeResolver TypeResolver { get; protected set; }

        //public MapperCollection<Type, Type> MappedTypes { get; protected set; }

        protected IEnumerable MapList(IEnumerable source, IDictionary<object, object> mappedObjects)
        {
            Type sourceType = source.GetType();
            Type enumerableType = sourceType.IsArray ? sourceType.GetElementType() : sourceType.GetGenericArguments().FirstOrDefault();
            if (enumerableType != null && !enumerableType.IsValueType)
            {
                var list = Activator.CreateInstance(typeof(ObservableCollection<>).MakeGenericType(enumerableType)) as IList;

                foreach (var element in source)
                {
                    list.Add(MapObject(element, mappedObjects));
                }

                return list;
            }
            return source;
        }

        public object MapObject(object source, IDictionary<object, object> mappedObjects)
        {
            if (source == null) return null;
            if (source is IEnumerable) return MapList(source as IEnumerable, mappedObjects);
            Type sourceType = source.GetType();
            if (sourceType.IsValueType) return source;

            if (!mappedObjects.ContainsKey(source))
            {
                Type destinationType = TypeResolver.Resolve(sourceType);
                object destination = Activator.CreateInstance(destinationType);

                var properties = sourceType.GetProperties().Where(p => p.CanRead && p.CanWrite).ToList();

                foreach (var propertyInfo in properties.Where(p => p.PropertyType.IsValueType))
                {
                    destinationType.GetProperty(propertyInfo.Name).SetValue(destination, propertyInfo.GetValue(source, null), null);
                }

                mappedObjects[source] = destination;

                foreach (var propertyInfo in properties.Where(p => !p.PropertyType.IsValueType))
                {
                    destinationType.GetProperty(propertyInfo.Name)
                        .SetValue(destination, MapObject(propertyInfo.GetValue(source, null), mappedObjects), null);
                }
            }
            return mappedObjects[source];
        }

        public object Map(object obj)
        {
            return MapObject(obj, new Dictionary<object, object>());
        }

        public T Map<T>(object obj)
        {
            return (T)Map(obj);
        }

        public void Register(Type first, Type second)
        {
            //MappedTypes.Add(first, second);
        }

        public void Register<TFirst, TSecond>()
        {
            //MappedTypes.Add(typeof(TFirst), typeof(TSecond));
        }

        public Tuple<TMappingInfo, TMappingInfo> Register<TMappingInfo>(Type first, Type second)
        {
            throw new NotSupportedException();
        }

        public Tuple<TMappingInfo, TMappingInfo> Register<TFirst, TSecond, TMappingInfo>()
        {
            throw new NotSupportedException();
        }
    }
}
